package matchs

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"time"

	"github.com/flipped-aurora/gin-vue-admin/server/global"
	"github.com/flipped-aurora/gin-vue-admin/server/model/matchs"
	"github.com/flipped-aurora/gin-vue-admin/server/model/system"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// AskMatchService 问问匹配服务
type AskMatchService struct{}

// AskMatchServiceApp 问问匹配服务实例
var AskMatchServiceApp = new(AskMatchService)

// DoAskMatch 根据问问任务的目标专业，筛选符合条件的用户并推送任务
// @param ctx 上下文
// @param askId 目标问问任务ID
// @param batchSize 本次推送用户数量，实时触发传10，定时补推传5
// @param tx 可选的事务对象，为nil时使用默认数据库连接并创建新事务
// @return 实际成功推送的用户数和匹配过程中的错误
func (s *AskMatchService) DoAskMatch(ctx context.Context, askId uint, batchSize int, tx *gorm.DB) (int, error) {
	// 使用传入的事务或默认数据库连接
	db := global.GVA_DB
	if tx != nil {
		db = tx
	}
	
	// 步骤1：校验任务有效性
	task, err := s.validateAskTaskWithDB(ctx, askId, db)
	if err != nil {
		return 0, err
	}

	// 步骤2：筛选符合条件的用户 - 传入数据库连接确保事务一致性
	targetUsers, err := s.filterTargetUsers(ctx, task, batchSize, db)
	if err != nil {
		global.GVA_LOG.Error("筛选目标用户失败", zap.Uint("askId", askId), zap.Error(err))
		return 0, err
	}

	if len(targetUsers) == 0 {
		global.GVA_LOG.Info("未找到符合条件的用户", zap.Uint("askId", askId))
		return 0, nil
	}

	// 步骤3：执行推送
	var pushedCount int
	if tx != nil {
		// 如果有事务，直接在事务中执行推送
		pushedCount, err = s.executePushWithTx(ctx, askId, targetUsers, task, tx)
	} else {
		// 如果没有事务，创建新事务执行推送
		pushedCount, err = s.executePush(ctx, askId, targetUsers, task)
	}
	
	if err != nil {
		global.GVA_LOG.Error("执行推送失败", zap.Uint("askId", askId), zap.Error(err))
		return 0, err
	}

	// 步骤4：更新任务状态（可选终止）- 使用相同的数据库连接
	err = s.updateTaskStatusWithDB(ctx, task, db)
	if err != nil {
		global.GVA_LOG.Error("更新任务状态失败", zap.Uint("askId", askId), zap.Error(err))
		// 不影响推送结果返回
	}

	// 步骤5：日志与返回
	global.GVA_LOG.Info("匹配推送完成", 
		zap.Uint("askId", askId), 
		zap.Int("batchSize", batchSize), 
		zap.Int("actualPushedCount", pushedCount))

	return pushedCount, nil
}

// validateAskTaskWithDB 使用指定数据库连接校验任务有效性
func (s *AskMatchService) validateAskTaskWithDB(ctx context.Context, askId uint, db *gorm.DB) (*matchs.MatchAskTask, error) {
	// 查询任务详情
	var task matchs.MatchAskTask
	err := db.WithContext(ctx).First(&task, askId).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("问问任务不存在")
		}
		return nil, fmt.Errorf("查询任务信息失败: %w", err)
	}

	// 校验任务状态
	if task.Status != matchs.MatchAskStatusMatching {
		return nil, errors.New("任务已终止匹配")
	}

	// 校验任务有效期
	if time.Now().In(global.LocTime).After(task.ExpireAt) {
		return nil, errors.New("任务已过期")
	}

	return &task, nil
}

// validateAskTask 校验任务有效性（保持向后兼容）
func (s *AskMatchService) validateAskTask(ctx context.Context, askId uint) (*matchs.MatchAskTask, error) {
	return s.validateAskTaskWithDB(ctx, askId, global.GVA_DB)
}

// filterTargetUsers 筛选符合条件的用户
func (s *AskMatchService) filterTargetUsers(ctx context.Context, task *matchs.MatchAskTask, batchSize int, db *gorm.DB) ([]uint64, error) {
	// 反序列化目标专业ID数组
	var targetProfessionalIDs []uint
	if err := json.Unmarshal([]byte(task.TargetProfessionalIDs), &targetProfessionalIDs); err != nil {
		return nil, fmt.Errorf("解析目标专业ID失败: %w", err)
	}

	// 基础筛选：根据专业ID查询用户
	professionalUserIDs, err := s.GetUsersByProfessionalIDsWithDB(ctx, targetProfessionalIDs, db)
	if err != nil {
		return nil, fmt.Errorf("查询专业用户失败: %w", err)
	}

	if len(professionalUserIDs) == 0 {
		return nil, nil
	}

	// 排除已推送用户
	validUserIDs, err := s.excludePushedUsersWithDB(ctx, task.ID, professionalUserIDs, db)
	if err != nil {
		return nil, fmt.Errorf("排除已推送用户失败: %w", err)
	}

	if len(validUserIDs) == 0 {
		return nil, nil
	}

	// 配额筛选：筛选出当日剩余接收配额>0的用户
	quotaValidUserIDs, err := s.filterQuotaValidUsersWithDB(ctx, validUserIDs, db)
	if err != nil {
		return nil, fmt.Errorf("筛选配额有效用户失败: %w", err)
	}

	if len(quotaValidUserIDs) == 0 {
		return nil, nil
	}

	// 随机取batchSize个用户
	return s.randomSampleUsers(quotaValidUserIDs, batchSize), nil
}

// GetUsersByProfessionalIDsWithDB 使用指定数据库连接根据专业ID批量查询用户
func (s *AskMatchService) GetUsersByProfessionalIDsWithDB(ctx context.Context, professionalIDs []uint, db *gorm.DB) ([]uint64, error) {
	var userIDs []uint64
	
	// 查询专业ID在目标列表中的用户
	err := db.WithContext(ctx).Model(&system.SysUser{}).
		Where("major IN ? AND auth_status & ? != 0", professionalIDs, global.AuthStatusMajor).
		Pluck("id", &userIDs).Error
		
	if err != nil {
		return nil, err
	}
	
	return userIDs, nil
}

// GetUsersByProfessionalIDs 根据专业ID批量查询用户（保持向后兼容）
func (s *AskMatchService) GetUsersByProfessionalIDs(ctx context.Context, professionalIDs []uint) ([]uint64, error) {
	return s.GetUsersByProfessionalIDsWithDB(ctx, professionalIDs, global.GVA_DB)
}

// excludePushedUsersWithDB 使用指定数据库连接排除已推送用户
func (s *AskMatchService) excludePushedUsersWithDB(ctx context.Context, askId uint, userIDs []uint64, db *gorm.DB) ([]uint64, error) {
	if len(userIDs) == 0 {
		return nil, nil
	}

	// 查询已推送记录
	var pushedUserIDs []uint64
	err := db.WithContext(ctx).Model(&matchs.MatchAskPush{}).
		Where("ask_task_id = ? AND receive_user_id IN ?", askId, userIDs).
		Pluck("receive_user_id", &pushedUserIDs).Error
	if err != nil {
		return nil, err
	}

	// 创建已推送用户的map用于快速查找
	pushedMap := make(map[uint64]bool)
	for _, uid := range pushedUserIDs {
		pushedMap[uid] = true
	}

	// 过滤出未推送的用户
	var validUserIDs []uint64
	for _, uid := range userIDs {
		if !pushedMap[uid] {
			validUserIDs = append(validUserIDs, uid)
		}
	}

	return validUserIDs, nil
}

// excludePushedUsers 排除已推送用户（保持向后兼容）
func (s *AskMatchService) excludePushedUsers(ctx context.Context, askId uint, userIDs []uint64) ([]uint64, error) {
	return s.excludePushedUsersWithDB(ctx, askId, userIDs, global.GVA_DB)
}

// GetUserReceiveQuotaWithDB 使用指定数据库连接查询用户当日剩余接收配额
func (s *AskMatchService) GetUserReceiveQuotaWithDB(ctx context.Context, userID uint64, db *gorm.DB) (int8, error) {
	today := time.Now().In(global.LocTime).Format("2006-01-02")
	todayTime, _ := time.Parse("2006-01-02", today)
	
	var quota matchs.MatchAskReceiveQuota
	err := db.WithContext(ctx).Where("user_id = ? AND date = ?", userID, todayTime).First(&quota).Error
	
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// 用户今日没有配额记录，创建新记录
			newQuota := matchs.MatchAskReceiveQuota{
				UserID:     userID,
				Date:       todayTime,
				TotalQuota: matchs.DefaultAskReceiveDailyQuota,
				UsedQuota:  0,
			}
			err = db.WithContext(ctx).Create(&newQuota).Error
			if err != nil {
				return 0, err
			}
			return newQuota.TotalQuota - newQuota.UsedQuota, nil
		}
		return 0, err
	}
	
	return quota.TotalQuota - quota.UsedQuota, nil
}

// GetUserReceiveQuota 查询用户当日剩余接收配额（保持向后兼容）
func (s *AskMatchService) GetUserReceiveQuota(ctx context.Context, userID uint64) (int8, error) {
	return s.GetUserReceiveQuotaWithDB(ctx, userID, global.GVA_DB)
}

// filterQuotaValidUsersWithDB 使用指定数据库连接筛选配额有效的用户
func (s *AskMatchService) filterQuotaValidUsersWithDB(ctx context.Context, userIDs []uint64, db *gorm.DB) ([]uint64, error) {
	var validUserIDs []uint64
	
	for _, userID := range userIDs {
		remainingQuota, err := s.GetUserReceiveQuotaWithDB(ctx, userID, db)
		if err != nil {
			global.GVA_LOG.Error("查询用户配额失败", zap.Uint64("userId", userID), zap.Error(err))
			continue
		}
		
		if remainingQuota > 0 {
			validUserIDs = append(validUserIDs, userID)
		}
	}
	
	return validUserIDs, nil
}

// filterQuotaValidUsers 筛选配额有效的用户（保持向后兼容）
func (s *AskMatchService) filterQuotaValidUsers(ctx context.Context, userIDs []uint64) ([]uint64, error) {
	return s.filterQuotaValidUsersWithDB(ctx, userIDs, global.GVA_DB)
}

// UpdateUserReceiveQuota 更新用户接收配额
func (s *AskMatchService) UpdateUserReceiveQuota(ctx context.Context, userID uint64) error {
	today := time.Now().In(global.LocTime).Format("2006-01-02")
	todayTime, _ := time.Parse("2006-01-02", today)
	
	db := global.GVA_DB.WithContext(ctx)
	result := db.Model(&matchs.MatchAskReceiveQuota{}).
		Where("user_id = ? AND date = ?", userID, todayTime).
		Update("used_quota", gorm.Expr("used_quota + ?", 1))
		
	if result.Error != nil {
		return result.Error
	}
	
	// 如果没有更新到记录，说明记录不存在，创建新记录
	if result.RowsAffected == 0 {
		newQuota := matchs.MatchAskReceiveQuota{
			UserID:     userID,
			Date:       todayTime,
			TotalQuota: matchs.DefaultAskReceiveDailyQuota,
			UsedQuota:  1,
		}
		return db.Create(&newQuota).Error
	}
	
	return nil
}

// randomSampleUsers 随机采样用户
func (s *AskMatchService) randomSampleUsers(userIDs []uint64, sampleSize int) []uint64 {
	if len(userIDs) <= sampleSize {
		return userIDs
	}
	
	// 创建随机数种子
	rand.Seed(time.Now().UnixNano())
	
	// 打乱数组
	shuffled := make([]uint64, len(userIDs))
	copy(shuffled, userIDs)
	for i := range shuffled {
		j := rand.Intn(i + 1)
		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
	}
	
	// 返回前sampleSize个
	return shuffled[:sampleSize]
}

// executePushWithTx 在事务中执行推送操作
func (s *AskMatchService) executePushWithTx(ctx context.Context, askId uint, userIDs []uint64, task *matchs.MatchAskTask, tx *gorm.DB) (int, error) {
	// 创建推送记录
	pushRecords := make([]matchs.MatchAskPush, 0, len(userIDs))
	now := time.Now().In(global.LocTime)
	
	for _, userID := range userIDs {
		pushRecords = append(pushRecords, matchs.MatchAskPush{
			AskTaskID:     uint64(askId),
			ReceiveUserID: userID,
			Status:        matchs.AskPushStatusPending,
			PushedAt:      now,
		})
	}
	
	// 批量创建推送记录
	if err := tx.CreateInBatches(pushRecords, 100).Error; err != nil {
		return 0, fmt.Errorf("创建推送记录失败: %w", err)
	}
	
	// 更新用户接收配额
	for _, userID := range userIDs {
		if err := s.updateUserReceiveQuotaInTx(tx, userID); err != nil {
			return 0, fmt.Errorf("更新用户配额失败: %w", err)
		}
	}
	
	// 更新任务推送计数
	if err := tx.Model(&task).Update("pushed_count", gorm.Expr("pushed_count + ?", len(userIDs))).Error; err != nil {
		return 0, fmt.Errorf("更新任务推送计数失败: %w", err)
	}
	
	return len(userIDs), nil
}

// executePush 执行推送（事务内操作）
func (s *AskMatchService) executePush(ctx context.Context, askId uint, userIDs []uint64, task *matchs.MatchAskTask) (int, error) {
	// 使用事务包装推送操作
	var pushedCount int
	err := global.GVA_DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var err error
		pushedCount, err = s.executePushWithTx(ctx, askId, userIDs, task, tx)
		return err
	})
	return pushedCount, err
}

// updateUserReceiveQuotaInTx 在事务中更新用户接收配额
func (s *AskMatchService) updateUserReceiveQuotaInTx(tx *gorm.DB, userID uint64) error {
	today := time.Now().In(global.LocTime).Format("2006-01-02")
	todayTime, _ := time.Parse("2006-01-02", today)
	
	result := tx.Model(&matchs.MatchAskReceiveQuota{}).
		Where("user_id = ? AND date = ?", userID, todayTime).
		Update("used_quota", gorm.Expr("used_quota + ?", 1))
		
	if result.Error != nil {
		return result.Error
	}
	
	if result.RowsAffected == 0 {
		newQuota := matchs.MatchAskReceiveQuota{
			UserID:     userID,
			Date:       todayTime,
			TotalQuota: matchs.DefaultAskReceiveDailyQuota,
			UsedQuota:  1,
		}
		return tx.Create(&newQuota).Error
	}
	
	return nil
}

// updateTaskStatusWithDB 使用指定数据库连接更新任务状态（可选终止）
func (s *AskMatchService) updateTaskStatusWithDB(ctx context.Context, task *matchs.MatchAskTask, db *gorm.DB) error {
	// 检查是否需要更新任务状态
	const maxPushedCount = 30 // 累计推送达30人
	
	// 检查推送计数
	if task.PushedCount >= maxPushedCount {
		return s.changeTaskStatusWithDB(ctx, task.ID, matchs.MatchAskStatusReplied, db)
	}
	
	// 检查是否有回复
	hasReply, err := s.checkHasReplyWithDB(ctx, task.ID, db)
	if err != nil {
		return err
	}
	
	if hasReply {
		return s.changeTaskStatusWithDB(ctx, task.ID, matchs.MatchAskStatusReplied, db)
	}
	
	return nil
}

// updateTaskStatus 更新任务状态（可选终止）（保持向后兼容）
func (s *AskMatchService) updateTaskStatus(ctx context.Context, task *matchs.MatchAskTask) error {
	return s.updateTaskStatusWithDB(ctx, task, global.GVA_DB)
}

// checkHasReplyWithDB 使用指定数据库连接检查任务是否有回复
func (s *AskMatchService) checkHasReplyWithDB(ctx context.Context, askId uint, db *gorm.DB) (bool, error) {
	var count int64
	err := db.WithContext(ctx).Model(&matchs.MatchAskReply{}).
		Where("task_id = ?", askId).
		Count(&count).Error
		
	if err != nil {
		return false, err
	}
	
	return count > 0, nil
}

// checkHasReply 检查任务是否有回复（保持向后兼容）
func (s *AskMatchService) checkHasReply(ctx context.Context, askId uint) (bool, error) {
	return s.checkHasReplyWithDB(ctx, askId, global.GVA_DB)
}

// changeTaskStatusWithDB 使用指定数据库连接更改任务状态
func (s *AskMatchService) changeTaskStatusWithDB(ctx context.Context, askId uint, status int8, db *gorm.DB) error {
	ups := map[string]interface{}{
		"status":       status,
		"completed_at": time.Now().In(global.LocTime),
	}
	return db.WithContext(ctx).Model(&matchs.MatchAskTask{}).Where("id = ?", askId).Updates(ups).Error
}

// changeTaskStatus 更改任务状态（保持向后兼容）
func (s *AskMatchService) changeTaskStatus(ctx context.Context, askId uint, status int8) error {
	return s.changeTaskStatusWithDB(ctx, askId, status, global.GVA_DB)
}